package com.ruoyi.common.utils.poi;

import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
import org.springframework.util.ReflectionUtils;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Word工具类
 * liuhl 2024-06-28
 */
public class MyWord {
	private final String path;
	private FileInputStream fi;
	private final XWPFDocument docx;
 
	/**
	 * 构造函数
	 *
	 * @param path: 文件路径
	 * @return: null
	 **/
	public MyWord(String path) throws Exception {
		File file = new File(path);
		if (!file.exists()) {
			throw new Exception("非文件路径！");
		}
		if (!path.toLowerCase().contains(".docx")) {
			throw new Exception("非docx文件！");
		}
		this.path = path.replace("/", File.separator);
		this.fi = new FileInputStream(file);
		this.docx = new XWPFDocument(this.fi);
	}
 
	/**
	 * 获取所有段落
	 *
	 * @param empty: 是否剔除空白段落
	 * @return: java.util.List<org.apache.poi.xwpf.usermodel.XWPFParagraph>
	 **/
	public List<XWPFParagraph> getParagraphs(boolean empty) throws Exception {
		List<XWPFParagraph> paragraphs = this.docx.getParagraphs();
		if (!empty) {
			return paragraphs;
		}
		//剔除空白段落
		List<XWPFParagraph> list = new ArrayList<>();
		for (XWPFParagraph paragraph : paragraphs) {
			String text = paragraph.getParagraphText();
			if (isNullStr(text)) {
				continue;
			}
			list.add(paragraph);
		}
		return list;
	}
 
	/**
	 * 获取特定段落
	 *
	 * @param inx: 子字符串
	 * @return: org.apache.poi.xwpf.usermodel.XWPFParagraph
	 **/
	public XWPFParagraph getParagraph(String inx) throws Exception {
		List<XWPFParagraph> paragraphs = getParagraphs(false);
		for (XWPFParagraph paragraph : paragraphs) {
			if (paragraph.getParagraphText().equals(inx)) {
				return paragraph;
			}
		}
		return paragraphs.get(0);
	}
 
	/**
	 * 获取所有表格
	 * @return: java.util.List<org.apache.poi.xwpf.usermodel.XWPFTable>
	 **/
	public List<XWPFTable> getTables() throws Exception {
		return this.docx.getTables();
	}
 
	/**
	 * 获取特定表格
	 *
	 * @param inx: 索引
	 * @return: org.apache.poi.xwpf.usermodel.XWPFTable
	 **/
	public XWPFTable getTable(int inx) throws Exception {
		List<XWPFTable> tables = getTables();
		return tables.get(inx);
	}
 
	/**
	 * 向段落里写入数据
	 *
	 * @param paragraph: 段落
	 * @param data:      数据
	 * @return: void
	 **/
	public void writeDataToParagraph(XWPFParagraph paragraph,
									 Map<String, String> data) throws Exception {
		String text = paragraph.getText();
		String resultText = replaceText(text, data);
		writeText(paragraph, resultText);
	}
 
	/**
	 * 向段落里写入数据
	 *
	 * @param paragraph: 段落
	 * @param data:      数据
	 * @param type:      类名路径
	 * @return: void
	 **/
	public void writeDataToParagraph(XWPFParagraph paragraph,
									 Object data,
									 String type) throws Exception {
		String text = paragraph.getText();
		String resultText = replaceText(text, data, type);
		writeText(paragraph, resultText);
	}
 
	/**
	 * 向表格里写入数据
	 *
	 * @param table: 表格
	 * @param data:  数据
	 * @return: void
	 **/
	public void writeDataToTable(XWPFTable table,
								 Map<String, String> data) throws Exception {
		List<XWPFTableRow> rows = table.getRows();
		for (XWPFTableRow row : rows) {
			List<XWPFTableCell> cells = row.getTableCells();
			for (XWPFTableCell cell : cells) {
				// 不能使用该方法直接加内容，这样会在原内容后面追加，并且不能保证跟原字体样式一致
				// cell.setText(resultText);
				List<XWPFParagraph> paragraphs = cell.getParagraphs();
				for (XWPFParagraph paragraph : paragraphs) {
					//向单元格的段落里写入数据
					writeDataToParagraph(paragraph, data);
				}
			}
		}
	}
 
	/**
	 * 向表格里写入数据
	 *
	 * @param table: 表格
	 * @param data:  数据
	 * @param type:  类名路径
	 * @return: void
	 **/
	public void writeDataToTable(XWPFTable table,
								 Object data,
								 String type) throws Exception {
		List<XWPFTableRow> rows = table.getRows();
		for (XWPFTableRow row : rows) {
			List<XWPFTableCell> cells = row.getTableCells();
			for (XWPFTableCell cell : cells) {
				List<XWPFParagraph> paragraphs = cell.getParagraphs();
				for (XWPFParagraph paragraph : paragraphs) {
					writeDataToParagraph(paragraph, data, type);
				}
			}
		}
	}
 
	/**
	 * 向表格里写入多条数据
	 *
	 * @param table:    表格
	 * @param dataList: 数据列表
	 * @param startRow: 起始行索引
	 * @return: void
	 **/
	public void writeDataListToTable(XWPFTable table,
									 List<Map<String, String>> dataList,
									 int startRow) throws Exception {
		for (Map<String, String> data : dataList) {
			//创建新的行
			CTRow ctrow = CTRow.Factory.parse(table.getRow(startRow).getCtRow().newInputStream());
			// 此方法可以使新增的行和模板样式一样，但是新行赋值是会将上面行的row也修改了
			// XWPFTableRow newRow = new XWPFTableRow(table.getRow(startRow).getCtRow(), table);
			XWPFTableRow newRow = new XWPFTableRow(ctrow, table);
			List<XWPFTableCell> cells = newRow.getTableCells();
			for (XWPFTableCell cell : cells) {
				List<XWPFParagraph> paragraphs = cell.getParagraphs();
				for (XWPFParagraph paragraph : paragraphs) {
					writeDataToParagraph(paragraph, data);
				}
			}
			table.addRow(newRow);
		}
		table.removeRow(startRow);
	}
 
	/**
	 * 向表格里写入多条数据
	 *
	 * @param table:    表格
	 * @param dataList: 数据列表
	 * @param startRow: 起始行索引，模板行
	 * @param type:     类名路径
	 * @return: void
	 **/
	public void writeDataListToTable(XWPFTable table,
									 List<Object> dataList,
									 int startRow,
									 String type) throws Exception {
		for (Object data : dataList) {
			CTRow ctrow = CTRow.Factory.parse(table.getRow(startRow).getCtRow().newInputStream());
			XWPFTableRow newRow = new XWPFTableRow(ctrow, table);
			List<XWPFTableCell> cells = newRow.getTableCells();
			for (XWPFTableCell cell : cells) {
				List<XWPFParagraph> paragraphs = cell.getParagraphs();
				for (XWPFParagraph paragraph : paragraphs) {
					writeDataToParagraph(paragraph, data, type);
				}
			}
			table.addRow(newRow);
		}
		table.removeRow(startRow);
	}
 
	/**
	 * 向单元格里写入数据
	 *
	 * @param tableInx: 表格索引
	 * @param row:      行索引
	 * @param col:      列索引
	 * @param text:     文本
	 * @return: void
	 **/
	public void writeDataToCell(int tableInx,
								int row,
								int col,
								String text) throws Exception {
		XWPFTable table = this.getTable(tableInx);
		writeDataToCell(table, row, col, text);
	}
 
	/**
	 * 向单元格里写入数据
	 *
	 * @param table: 表格
	 * @param row:   行索引
	 * @param col:   列索引
	 * @param text:  文本
	 * @return: void
	 **/
	public void writeDataToCell(XWPFTable table,
								int row,
								int col,
								String text) throws Exception {
		XWPFTableCell cell = getCell(table, row, col);
		cell.setText(text);
	}
 
	/**
	 * 向单元格里插入图片
	 *
	 * @param tableInx: 表格索引
	 * @param row:      行索引
	 * @param col:      列索引
	 * @param imgPath:  图片路径
	 * @param width:    宽
	 * @param height:   高
	 * @return: void
	 **/
	private void writePictureToCell(int tableInx,
									int row,
									int col,
									String imgPath,
									int width,
									int height) throws Exception {
		XWPFTable table = this.getTable(tableInx);
		writePictureToCell(table, row, col, imgPath, width, height);
	}
 
	/**
	 * 向单元格里插入图片
	 *
	 * @param table:   表格
	 * @param row:     行索引
	 * @param col:     列索引
	 * @param imgPath: 图片路径
	 * @param width:   宽
	 * @param height:  高
	 * @return: void
	 **/
	private void writePictureToCell(XWPFTable table,
									int row,
									int col,
									String imgPath,
									int width,
									int height) throws Exception {
		XWPFTableCell cell = getCell(table, row, col);
		//获取图片
		File image = new File(imgPath);
		if (!image.exists()) {
			throw new Exception("未发现图片！");
		}
		byte format;
		if (imgPath.endsWith(".emf")) {
			format = 2;
		} else if (imgPath.endsWith(".wmf")) {
			format = 3;
		} else if (imgPath.endsWith(".pict")) {
			format = 4;
		} else if (!imgPath.endsWith(".jpeg") && !imgPath.endsWith(".jpg")) {
			if (imgPath.endsWith(".png")) {
				format = 6;
			} else if (imgPath.endsWith(".dib")) {
				format = 7;
			} else if (imgPath.endsWith(".gif")) {
				format = 8;
			} else if (imgPath.endsWith(".tiff")) {
				format = 9;
			} else if (imgPath.endsWith(".eps")) {
				format = 10;
			} else if (imgPath.endsWith(".bmp")) {
				format = 11;
			} else {
				if (!imgPath.endsWith(".wpg")) {
					throw new Exception("图片格式不正确！");
				}
 
				format = 12;
			}
		} else {
			format = 5;
		}
		//插入图片
		List<XWPFParagraph> paragraphs = cell.getParagraphs();
		XWPFParagraph newPara = paragraphs.get(0);
		XWPFRun imageCellRun = newPara.createRun();
		FileInputStream is = new FileInputStream(imgPath);
		imageCellRun.addPicture(is, format, image.getName(), Units.toEMU(width), Units.toEMU(height));
	}
 
	/**
	 * 合并单元格
	 *
	 * @param tableInx: 表格索引
	 * @param startRow: 左上角单元格行索引
	 * @param startCol: 左上角单元格列索引，(0, 0)
	 * @param endRow:   右下角单元格行索引
	 * @param endCol:   右下角单元格列索引，(1, 1)
	 * @return: void
	 **/
	public XWPFTableCell mergeCells(int tableInx,
									int startRow,
									int startCol,
									int endRow,
									int endCol) throws Exception {
		XWPFTable table = this.getTable(tableInx);
		return mergeCells(table, startRow, startCol, endRow, endCol);
	}
 
	/**
	 * 合并单元格
	 *
	 * @param table:    表格
	 * @param startRow: 左上角单元格行索引
	 * @param startCol: 左上角单元格列索引，(0, 0)
	 * @param endRow:   右下角单元格行索引
	 * @param endCol:   右下角单元格列索引，(1, 1)
	 * @return: void
	 **/
	public XWPFTableCell mergeCells(XWPFTable table,
									int startRow,
									int startCol,
									int endRow,
									int endCol) throws Exception {
		//插入的表格行使用此方法合并无效果
		if (startRow == endRow) {
			//直接纵向合并
			table.getRow(startRow).getCell(startCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
			table.getRow(endRow).getCell(endCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
		} else {
			//先横向合并
			for (int i = startRow; i <= endRow; i++) {
				table.getRow(i).getCell(startCol).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
				table.getRow(i).getCell(endCol).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
			}
			//再纵向合并
			table.getRow(startRow).getCell(startCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
			table.getRow(endRow).getCell(startCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
		}
		return getCell(table, startRow, startCol);
	}
 
	/**
	 * 保存并关闭文件
	 *
	 * @param path:  保存路径
	 * @param close: 是否关闭文件
	 * @return: void
	 **/
	public void saveAndClose(String path,
							 boolean close) throws Exception {
		FileOutputStream fo = null;
		try {
			if (isNullStr(path)) {
				fo = new FileOutputStream(this.path);
			} else {
				//另存为
				fo = new FileOutputStream(path);
			}
			//保存
			this.docx.write(fo);
			//关闭
			if (close) {
				close();
			}
		} catch (Exception e) {
			close();
			throw new Exception("保存失败！", e);
		} finally {
			if (fo != null) {
				fo.close();
			}
		}
	}
 
	//替换文本，我叫${name} -> 我叫michael
	private String replaceText(String str,
							   Map<String, String> map) throws Exception {
		if (isNullStr(str)) {
			return "";
		}
		//匹配占位符${name}
		String resultStr = str;
		Pattern pattern = Pattern.compile("\\$\\{.*?}");
		Matcher matcher = pattern.matcher(str);
		while (matcher.find()) {
			String group = matcher.group();
			//匹配字段名
			//此正则匹配的文本带{}
			//Pattern fieldPattern = Pattern.compile("\\{.*?}");
			Pattern fieldPattern = Pattern.compile("(?<=\\{)[^}]+");
			Matcher fieldMatcher = fieldPattern.matcher(group);
			String field = "";
			if (fieldMatcher.find()) {
				field = fieldMatcher.group();
			}
			//获取数据
			String s = map.getOrDefault(field, "");
			//数据替换占位符
			resultStr = resultStr.replace(group, toNotNull(s));
		}
		return resultStr;
	}
 
	//替换文本，我叫${name} -> 我叫michael
	private String replaceText(String str,
							   Object object,
							   String type) throws Exception {
		if (isNullStr(str)) {
			return "";
		}
		//匹配占位符${name}
		String resultStr = str;
		Pattern pattern = Pattern.compile("\\$\\{.*?}");
		Matcher matcher = pattern.matcher(str);
		while (matcher.find()) {
			String group = matcher.group();
			//匹配字段名
			Pattern fieldPattern = Pattern.compile("(?<=\\{)[^}]+");
			Matcher fieldMatcher = fieldPattern.matcher(group);
			String field = "";
			if (fieldMatcher.find()) {
				field = fieldMatcher.group();
			}
			//反射获取数据
			Class<?> aClass = Class.forName(type);
			String s = "";
			if (!isNullStr(field) && ReflectionUtils.findField(aClass, field) != null) {
				String methodStr = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
				Method method = aClass.getMethod(methodStr);
				s = String.valueOf(method.invoke(object));
				//数据替换占位符
				resultStr = resultStr.replace(group, toNotNull(s));
			}
		}
		return resultStr;
	}
 
	//将替换后的文本写入段落
	private void writeText(XWPFParagraph paragraph,
						   String str) throws Exception {
		boolean tag = false;
		for (XWPFRun run : paragraph.getRuns()) {
			if (tag) {
				//清除多余的run
				run.setText("", 0);
			} else {
				//要深入到run替换内容才能保证样式一致
				run.setText(str, 0);
				tag = true;
			}
		}
	}
 
	//获取单元格
	private XWPFTableCell getCell(XWPFTable table,
								  int row,
								  int col) throws Exception {
		XWPFTableRow tableRow = table.getRow(row);
		return tableRow.getCell(col);
	}
 
	//判断字符串是否为空
	private Boolean isNullStr(String str) {
		return str == null || Objects.equals(str, "");
	}
 
	//判断字符串是否为空
	private String toNotNull(String str) {
		return str == null ? "" : str;
	}
 
	//关闭文件
	private void close() throws Exception {
		if (this.fi != null) {
			this.fi.close();
			this.fi = null;
		}
	}
}